﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace GameDataTempshitEditor.Widgets
{
    [ToolboxItem(true)]
    [ToolboxBitmap(typeof(PictureBox))]
    public partial class DataPropertyGrid : Control
    {
        public DataPropertyGrid()
        {
            InitializeComponent();

            _Style = new DataPropertyGridDrawStyle();
            _Actions = new List< PropertyItemAction >();
            _ActionButtons = new List<ActionButton>();
            _Settings = new PropertyItemSettings();

            SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.EnableNotifyMessage, true);
        }

        //---------------------

        public PropertyItemBase Root
        {
            get
            {
                return _Root;
            }
            set
            {
                CloseActiveEditor();
                CloseActiveActions();

                _Selected = null;
                _Root = value;
                _YOffset = 0;
                _MouseCaptureMode = 0;

                if (_Root != null)
                    _Root.Settings = _Settings;

                Refresh();
            }
        }

        public ImageList ImageList { get; set; }

        //---------------------


        protected override void OnMouseCaptureChanged(EventArgs e)
        {
            base.OnMouseCaptureChanged(e);
            _MouseCaptureMode = 0;
            Refresh();
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);

            if (e.Button == MouseButtons.Left)
            {
                if (_MouseCaptureMode == 1)
                {
                    _MouseCaptureMode = 0;
                    Capture = false;
                }
            }
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e)
        {
            base.OnMouseDoubleClick( e );

            // toggle expand
            if (_Root != null)
            {
                int yOffsetLocal = -_YOffset;
                var action = _Root.HitTestAction(e.X, e.Y, _Style, Width, 0, ref yOffsetLocal);

                var selectedItem = (action != null) ? action.Item : null;
                SelectItem(selectedItem);

                ToggleSelectionExpand();
            }
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            Focus();

            if (e.Button == MouseButtons.Left)
            {
                // internal splitter
                if (Math.Abs(e.X - _Style.XSplit) < 5)
                {
                    _MouseCaptureMode = 1;
                    Capture = true;
                }

                // generic action
                else if (_Root != null)
                {
                    int yOffsetLocal = -_YOffset;
                    var action = _Root.HitTestAction(e.X, e.Y, _Style, Width, 0, ref yOffsetLocal);

                    var selectedItem = (action != null) ? action.Item : null;

                    SelectItem(selectedItem);

                    if (action != null)
                    {
                        string errorMessage = "";
                        if (!action.Perform(ref errorMessage))
                        {
                            ErrorBox.ShowError("Action error:\r\n" + errorMessage);
                        }
                        else
                        {
                            Refresh();
                        }
                    }
                }
            }
        }

        protected int DetermineElementYOffset(PropertyItemBase item)
        {
            List<PropertyItemBase> allElems = new List<PropertyItemBase>();
            _Root.Linearize(ref allElems);

            int index = allElems.IndexOf(_Selected);
            if (index >= allElems.Count)
                return 0;

            int totalYPos = 0;
            for (int i = 0; i < index; ++i)
            {
                totalYPos += allElems[i].Height;
            }

            return totalYPos;
        }
       
        protected void SelectItem(PropertyItemBase item)
        {
            if (item != _Selected)
            {
                if (CloseActiveEditor( true /*cancel*/ ))
                {
                    // close actions
                    CloseActiveActions();

                    // make the element selected
                    _Selected = item;

                    // measure the element Y position in the tree
                    if (item != null)
                    {
                        var elementYTop = DetermineElementYOffset(item);
                        var elementYBottom = item.Height + elementYTop;

                        if (_YOffset < (elementYBottom - Height))
                            _YOffset = elementYBottom - Height;

                        if (elementYTop < _YOffset)
                            _YOffset = elementYTop;

                        // create the actions and value editor
                        CreateActions();
                        CreateEditor();
                        RefreshElementsPlacement();
                    }

                    // redraw
                    Refresh();
                }
            }
        }

        public Boolean ShowAdvancedProperties
        {
            get
            {
                return _Settings.ShowAdvancedProperties;
            }
            set
            {
                if (_Settings.ShowAdvancedProperties != value)
                {
                    _Settings.ShowAdvancedProperties = value;
                    RefreshValues();
                }
            }
        }

        public void RefreshValues()
        {
            CloseActiveEditor(true);
            CloseActiveActions();

            Refresh();

            if (_Root != null)
            {
                _Root.Settings = _Settings;
                _Root.Refresh();
            }

            //RefreshItemLayout();
            Refresh();
            Invalidate();
        }

        private List< PropertyItemAction > _Actions;
        private List< ActionButton > _ActionButtons;

        private class ActionButton : Button
        {
            public ActionButton(DataPropertyGrid grid, PropertyItemBase item, PropertyItemAction action, ImageList images)
            {
                if (action.ImageIndex != -1)
                {
                    ImageList = images;
                    ImageIndex = action.ImageIndex;
                }
                else
                {
                    Text = action.Name;
                    AutoSize = true;
                }

                Action = action;
                Item = item;
                Width = 20;
                Height = 20;
                Grid = grid;

                Click += ActionButton_Click;
            }

            private DataPropertyGrid Grid;
            private PropertyItemAction Action;
            private PropertyItemBase Item;

            void ActionButton_Click(object sender, EventArgs e)
            {
                string errorMessage = "";
                if (!Action.Perform(ref errorMessage))
                {
                    ErrorBox.ShowError("Action error:\r\n" + errorMessage);
                }

                Grid.RefreshItemLayout();
            }

            public void RefreshPlacement(ref int xRight)
            {
                Top = Item.ValueRect.Top;
                Left = xRight - Width;
                Height = Item.ValueRect.Height;
                xRight -= Width;
            }
        }

        public void RefreshItemLayout()
        {
            // close input
            CloseActiveEditor( /*cancel*/ true);
            CloseActiveActions();

            // redraw whole thing
            Refresh();

            // update the data consistency
            if (_Root != null)
                _Root.Refresh();

            // recreate
            if (_Selected != null)
            {

                // HACK: sometimes the selection item get's deleted or recreated, refocus on anthing that's IN THE SAME place as our original element
                {
                    // get the vertical position of the selected item
                    var originalRect = _Selected.ValueRect;
                    int ypos = _Selected.ValueRect.Top + 10;

                    // find element at that location and reselect it
                    int yOffsetLocal = -_YOffset;
                    var hitTest = _Root.HitTestAction(1, ypos, _Style, Width, 0, ref yOffsetLocal);

                    // select
                    _Selected = (hitTest != null) ? hitTest.Item : null;
                }

                // recreate new actions
                CreateActions();
                CreateEditor();

                // make sure placement of action buttons is ok
                RefreshElementsPlacement();
            }

            // redraw whole thing
            Refresh();
                
        }

        private void CreateEditor()
        {
            CloseActiveEditor(true);

            if (_Selected != null)
            {
                _CurrentEditor = _Selected.CreateEditor();
                if (_CurrentEditor != null)
                {
                    var control = _CurrentEditor.CreateControls();
                    if (control != null)
                    {
                        control.BackColor = _Style.SelectionColor;

                        this.Controls.Add(control);
                        control.Focus();
                    }

                    // after value is changed refresh the layout
                    _CurrentEditor.OnValueCached += _CurrentEditor_OnValueCached;
                }
            }
        }

        void _CurrentEditor_OnValueCached(object sender)
        {
            RefreshItemLayout();
        }

        private void CreateActions()
        {
            CloseActiveActions();

            if (_Selected != null)
            {
                _Actions = _Selected.CreateActions();

                foreach (var action in _Actions)
                {
                    var b = new ActionButton(this, _Selected, action, ImageList);
                    _ActionButtons.Add(b);
                    Controls.Add(b);
                }
            }
        }

        protected bool CloseActiveEditor(bool cancelEditMode = false)
        {
            // remove the control
            if (_CurrentEditor != null)
            {
                if (!_CurrentEditor.DestroyControls( cancelEditMode ) )
                {
                    return false;
                }

                _CurrentEditor = null;;
                Refresh();
            }

            return true;
        }

        private void CloseActiveActions()
        {
            foreach (var actionButton in _ActionButtons)
            {
                Controls.Remove(actionButton);
            }

            _Actions.Clear();
            _ActionButtons.Clear();
        }

        protected override bool IsInputKey(Keys keyData)
        {
            switch (keyData)
            {
                case Keys.Up:
                case Keys.Down:
                    return true;
                case Keys.Shift | Keys.Up:
                case Keys.Shift | Keys.Down:
                    return true;
            }
            return base.IsInputKey(keyData);
        }

        public void HandleKey(Keys key)
        {
            if (key == Keys.Down)
            {
                NavigateElements(1);
            }
            else if (key == Keys.Up)
            {
                NavigateElements(-1);
            }
            else if (key == Keys.PageDown)
            {
                NavigateElements(10);
            }
            else if (key == Keys.PageUp)
            {
                NavigateElements(-10);
            }
            else if (key == Keys.Escape)
            {
                if (_Selected != null)
                {
                    CloseActiveEditor( /*cancel*/ true );
                    Refresh();
                }
            }
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            HandleKey(e.KeyCode);

            if (e.KeyCode == Keys.Enter)
            {
                ToggleSelectionExpand();          
            }
        }

        private void ToggleSelectionExpand()
        {
            if (_Selected != null && _Selected.CanExpand)
            {
                if (_Selected.IsExpanded)
                    _Selected.Collapse();
                else
                    _Selected.Expand();

                Refresh();
            }
        }

        protected void NavigateElements(int dir)
        {
            if (_Root != null)
            {
                List<PropertyItemBase> allElems = new List<PropertyItemBase>();
                _Root.Linearize( ref allElems );

                var nextSelected = _Root;
                int index = allElems.IndexOf( _Selected );
                if ( index < allElems.Count )
                {
                    int newIndex = Math.Max( 0, Math.Min( allElems.Count-1, index + dir ) );
                    nextSelected = allElems[newIndex];
                }

                SelectItem( nextSelected );
            }
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            Graphics g = pe.Graphics;
            base.OnPaint(pe);

            // size
            int w = this.Width;
            int h = this.Height;

            // background
            g.Clear(Color.FromArgb(255, 255, 255));

            // draw elements
            if (_Root != null)
            {
                int yOffsetLocal = -_YOffset;
                _Style.Selection = _Selected;
                _Root.Render(g, _Style, w, 0, ref yOffsetLocal);

                // update the total width
                _YMax = (yOffsetLocal + _YOffset); // total height of items
                _YScrollRange = Math.Max(0, _YMax - h); // limit the scroll range
                _YOffset = Math.Min(Math.Max(0, _YOffset), _YScrollRange); // limit the YRange
            }

            // refresh placement of action buttons and value editors (must happen after normal redraw)
            RefreshElementsPlacement();
        }

        private void RefreshElementsPlacement()
        {
            // place and measure actions
            int xRight = this.Width;
            foreach (var act in _ActionButtons)
            {
                act.RefreshPlacement(ref xRight);
            }

            // refresh the custom editor
            if (_CurrentEditor != null)
            {
                int actionMargin = (this.Width - xRight);
                _CurrentEditor.RefreshPlacement(actionMargin);
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (_MouseCaptureMode == 0)
            {
                UpdateCursor(e.X, e.Y);
            }
            else if (_MouseCaptureMode == 1)
            {
                _Style.XSplit = e.X;
                Refresh();
            }           
        }

        protected override void OnMouseWheel(MouseEventArgs e)
        {
            base.OnMouseWheel(e);

            int newYOffset = _YOffset + - e.Delta;
            newYOffset = Math.Min(Math.Max(0, newYOffset), _YScrollRange);
            if (newYOffset != _YOffset)
            {
                _YOffset = newYOffset;
                Refresh();
            }
        }

        private void UpdateCursor(int x, int y)
        {
            var cursor = Cursors.Arrow;

            if (Math.Abs(x - _Style.XSplit) < 5)
            {
                cursor = Cursors.SizeWE;
            }
            else
            {
                if (_Root != null)
                {
                    int yOffsetLocal = -_YOffset;
                    var action = _Root.HitTestAction(x, y, _Style, Width, 0, ref yOffsetLocal);
                    if (null != action)
                    {
                        cursor = action.MouseCursor;
                    }
                }
            }

            this.Cursor = cursor;
        }
    
        private PropertyItemBase _Root;
        private PropertyItemBase _Selected;
        private DataPropertyGridDrawStyle _Style;

        private PropertyItemEditor _CurrentEditor;

        private int _MouseCaptureMode;
        private int _YOffset;
        private int _YMax;
        private int _YScrollRange;

        private PropertyItemSettings _Settings;
    }

    public class DataPropertyGridDrawStyle
    {
        public DataPropertyGridDrawStyle()
        {
            TextFormatNormal = new StringFormat();
            TextFormatNormal.LineAlignment = StringAlignment.Center;
            TextFormatNormal.Alignment = StringAlignment.Near;
            TextFormatNormal.Trimming = StringTrimming.EllipsisCharacter;
            TextFormatNormal.FormatFlags = StringFormatFlags.NoWrap;

            TextGeneralFont = new Font("Arial", 8);
            TextBoldFont = new Font("Arial", 8, FontStyle.Bold);
            TextItalicFont = new Font("Arial", 8, FontStyle.Italic);
            TextCrossedFont = new Font("Arial", 8, FontStyle.Strikeout);
            TextBrushNormal = new SolidBrush(Color.FromArgb(20, 20, 20));
            TextBrushGrayedOut = new SolidBrush(Color.FromArgb(160, 160, 160));
            TextBrushSelectedInv = new SolidBrush(Color.FromArgb(255, 255, 255, 255));

            HoverFont = new Font("Arial", 7);
            HoverFontFormat = new StringFormat();
            HoverFontFormat.LineAlignment = StringAlignment.Near;
            HoverFontFormat.Alignment = StringAlignment.Near;
            HoverTextBrush = new SolidBrush(Color.FromArgb(80, 80, 80));
            HoverBoxBrush = new SolidBrush(Color.FromArgb(190,200,200,80));
            HoverBoxPen = new Pen(Color.FromArgb(0, 0, 0), 1.0f);

            SelectionColor = Color.FromArgb(255, 200, 200, 255);
            SelectionRectBrush = new SolidBrush(SelectionColor);
            SelectionRectPen = new Pen(Color.FromArgb(100, 100, 255));

            GridPen = new Pen(Color.FromArgb(220, 220, 220));

            BackgroundBrushNormal = new SolidBrush(Color.FromArgb(255, 255, 255, 255));
            BackgroundBrushGroup = new SolidBrush(Color.FromArgb(255, 200, 200, 200 ));
            BackgroundBrushError = new SolidBrush(Color.FromArgb(255, 255, 220, 220));
            BackgroundPenError = new Pen(Color.FromArgb(255, 255, 180, 180), 3);

            ExpandIconBrush = new SolidBrush(Color.FromArgb(200, 200, 200, 255));
            ExpandIconPen = new Pen(Color.FromArgb(60, 60, 60));

            XSplit = 220;
            XMargin = 20;

            ItemMargin = 15;
            ExpandIconSize = 10;

            ItemActionSize = 15;
            ItemActionMargin = 2;

            Selection = null;   
        }

        public readonly Font TextGeneralFont;
        public readonly Font TextBoldFont;
        public readonly Font TextItalicFont;
        public readonly Font TextCrossedFont;
        public readonly StringFormat TextFormatNormal;
        public readonly Brush TextBrushNormal;
        public readonly Brush TextBrushGrayedOut;
        public readonly Brush TextBrushSelectedInv;

        public readonly Brush BackgroundBrushNormal;
        public readonly Brush BackgroundBrushError;
        public readonly Brush BackgroundBrushGroup;
        public readonly Pen BackgroundPenError;

        public readonly Font HoverFont;
        public readonly StringFormat HoverFontFormat;
        public readonly Brush HoverTextBrush;
        public readonly Brush HoverBoxBrush;
        public readonly Pen HoverBoxPen;

        public readonly Brush SelectionRectBrush;
        public readonly Pen SelectionRectPen;
        public readonly Color SelectionColor;

        public readonly Brush ExpandIconBrush;
        public readonly Pen ExpandIconPen;

        public readonly Pen GridPen;

        public readonly int XMargin;
        public readonly int ItemMargin;
        public readonly int ItemActionSize;
        public readonly int ItemActionMargin;
        public readonly int ExpandIconSize;

        public PropertyItemBase Selection;
        public int XSplit;
    }

    public class PropertyEventArgs : EventArgs
    {
        public PropertyItemBase PropertyItem { get; private set; }
        public string IncomingValue { get; private set; }
        public bool Cancel { get; set; }

        public PropertyEventArgs(PropertyItemBase propertyItem, string incomingValue = "")
        {
            PropertyItem = propertyItem;
            IncomingValue = incomingValue;
            Cancel = false;
        }
    }

}
